﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Borg.AttributeAnalyze;
using Borg.AttributeAnalyze.Result;
using Properties;

namespace Borg
{
    public class PlugTypeInfo : RepositoryMetaElement
    {
        internal static int nextId = 1;

        internal MethodInfo MiOnPlugging = null;
        internal MethodInfo MiOnPlugged = null;

        internal MethodInfo MiOnUnplugging = null;
        internal MethodInfo MiOnUnplugged = null;

        internal const QualificationState unqualifiedState =
            QualificationState.QualificationMissing |
            QualificationState.PlugNameAlreadyExists |
            QualificationState.PlugSlotDefinitionNotFound |
            QualificationState.PlugSlotInterfaceNotImplemented |
            QualificationState.PlugParamValuesMissing |
            QualificationState.PlugParamValueNotAssignable;

        internal const QualificationState warningState =
            QualificationState.None;

        internal ExtensionTypeInfo extensionTypeInfo;
        internal readonly PlugAttribute plugAttribute;
        internal RepositoryCollection<ParamValue> paramValues;
        internal RepositoryCollection<SlotInfo> registeredInSlots;
        internal SlotDefinition slotDefinition;
        internal bool eventHandlerMethodInfosSet;

        public event CancelPlugEventHandler Plugging;
        public event PlugEventHandler Plugged;

        public event CancelPlugEventHandler Unplugging;
        public event PlugEventHandler Unplugged;

        private readonly string _name;

        public ExtensionTypeInfo ExtensionTypeInfo
        {
            get { return extensionTypeInfo; }
        }

        public SlotDefinition SlotDefinition
        {
            get
            {
                if (slotDefinition == null)
                    throw new InvalidOperationException("Plug is not qualified");

                return slotDefinition;
            }
        }

        public override string Name
        {
            get { return _name; }
        }

        public override Assembly Assembly
        {
            get { return extensionTypeInfo.pluginInfo.assembly; }
        }

        public RepositoryCollection<ParamValue> ParamValues
        {
            get { return paramValues; }
        }

        public RepositoryCollection<SlotInfo> RegisteredInSlots
        {
            get { return registeredInSlots; }
        }

        public bool AutoRegister
        {
            get { return plugAttribute.AutoRegister; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoRegister(this, value);
            }
        }

        public bool AutoPlug
        {
            get { return plugAttribute.AutoPlug; }
            set
            {
                if (!Runtime.IsCompositionAllowed)
                    throw new InvalidOperationException("Composition not allowed");

                Runtime.Composer.SetAutoPlug(this, value);
            }
        }

        public IEnumerable<KeyValuePair<string, string>> EventHandlers
        {
            get
            {
                yield return new KeyValuePair<string, string>("OnPlugging", plugAttribute.OnPlugging);
                yield return new KeyValuePair<string, string>("OnPlugged", plugAttribute.OnPlugged);

                yield return new KeyValuePair<string, string>("OnUnplugging", plugAttribute.OnUnplugging);
                yield return new KeyValuePair<string, string>("OnUnplugged", plugAttribute.OnUnplugged);
            }
        }

        public override bool IsQualified
        {
            get
            {
                return (qualificationState &
                    (QualificationState.QualificationMissing |
                    QualificationState.PlugNameAlreadyExists |
                    QualificationState.PlugSlotDefinitionNotFound |
                    QualificationState.PlugSlotInterfaceNotImplemented |
                    QualificationState.PlugParamValuesMissing |
                    QualificationState.PlugParamValueNotAssignable)) ==
                    QualificationState.None;
            }
        }

        internal bool QualificationMissing
        {
            set
            {
                if (value)
                {
                    var plugTypeInfo = this;
                    var state = (long)(plugTypeInfo.qualificationState | QualificationState.QualificationMissing);
                    plugTypeInfo.qualificationState = (QualificationState)state;

                    for (var i = paramValues.Count - 1; i >= 0; --i)
                        if (paramValues[i].defaultValue)
                            paramValues.Remove(paramValues[i]);
                }
                else
                {
                    var plugTypeInfo = this;
                    var state = (long)(plugTypeInfo.qualificationState & ~QualificationState.QualificationMissing);
                    plugTypeInfo.qualificationState = (QualificationState)state;
                }
            }
        }

        static PlugTypeInfo()
        { }

        internal PlugTypeInfo(ExtensionTypeInfo extensionTypeInfo, PlugAttribute plugAttribute)
        {
            id = nextId++;
            this.plugAttribute = plugAttribute;
            _name = plugAttribute.ForSlot;
            this.extensionTypeInfo = extensionTypeInfo;
            registeredInSlots = new RepositoryCollection<SlotInfo>(new List<SlotInfo>());
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Register()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Register(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Register(SlotInfo si)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Register(this, si);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Deregister()
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Deregister(this);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Deregister(SlotInfo si)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Deregister(this, si);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Plug(SlotInfo si)
        {
            if (!Runtime.IsCompositionAllowed)
                throw new InvalidOperationException("Composition not allowed");

            Runtime.Composer.Plug(this, si);
        }

        internal override QualificationState InternalQualify()
        {
            InternalMainQualify();

            return IsQualified
                ? VerifyEventHandlerMethods(true)
                : qualificationState;
        }

        internal void InternalRegister(bool isManual, uint taskGroupId, Assembly initiator)
        {
            foreach (var si in
                from eti in Runtime.Repository.ExtensionTypeInfos
                where eti.SlotTypeInfos[Name] != null
                from ei in eti.ExtensionInfos
                from si in ei.SlotInfos.Find(Name)
                select si)
                si.InternalRegister(this, isManual, taskGroupId, initiator, true);
        }

        internal void InternalDeregister(Assembly initiator, bool force)
        {
            for (var i = registeredInSlots.Count - 1; i >= 0; --i)
                registeredInSlots[i].InternalDeregister(this, initiator, force);
        }

        public T GetParamValue<T>(string name)
        {
            return (T)GetParamValue(name);
        }

        public object GetParamValue(string name)
        {
            var qualifiedParamValue = GetQualifiedParamValue(name);

            if (qualifiedParamValue != null)
                return qualifiedParamValue.Value;

            if (slotDefinition != null)
                return (from paramDefinition in slotDefinition.paramDefinitions.Find(name)
                        where
                            paramDefinition.IsQualified &&
                            paramDefinition.DefaultValue != ParamDefinition.NoDefaultValue
                        select paramDefinition.DefaultValue).FirstOrDefault();

            return null;
        }

        internal ParamValue GetQualifiedParamValue(string name)
        {
            return paramValues.FirstOrDefault(
                paramValue => paramValue.Name == name &&
                    (paramValue.IsQualified ||
                    paramValue.IsQualificationMissing &&
                    paramValue.ForceQualify()));
        }

        public static object ConvertFromString(string value, Type type)
        {
            return type.IsEnum
                ? Enum.Parse(type, value, true)
                : Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
        }

        internal QualificationState InternalMainQualify()
        {
            var builder = new StringBuilder();
            slotDefinition = Runtime.Repository.GetQualifiedSlotDefinition(Name);

            if (slotDefinition == null)
            {
                builder.AppendFormat("\n  {0}", Messages.PlugSlotDefinitionNotFound);
                var pti = this;
                var state = (long)(pti.qualificationState | QualificationState.PlugSlotDefinitionNotFound);
                pti.qualificationState = (QualificationState)state;
            }
            else
            {
                var pti = this;
                var state = (long)(pti.qualificationState & ~QualificationState.PlugSlotDefinitionNotFound);
                pti.qualificationState = (QualificationState)state;

                if (extensionTypeInfo.type.GetInterface(slotDefinition.FullTypeName, false) == null)
                {
                    builder.AppendFormat(
                        "\n  {0}",
                        string.Format(
                            Messages.PlugInterfaceNotImplemented,
                            extensionTypeInfo.Name,
                            slotDefinition.FullTypeName,
                            slotDefinition.contractInfo.Name));

                    var plugType = this;
                    var status = (long)(plugType.qualificationState | QualificationState.PlugSlotInterfaceNotImplemented);
                    plugType.qualificationState = (QualificationState)status;
                }

                foreach (var paramDef in slotDefinition.paramDefinitions)
                {
                    if (!paramDef.IsQualified && paramDef.IsQualificationMissing)
                    {
                        paramDef.ForceQualify();

                        if (!paramDef.IsQualified)
                            continue;
                    }

                    var qualifiedParamValue = GetQualifiedParamValue(paramDef.Name);

                    if (qualifiedParamValue == null)
                    {
                        if (paramDef.DefaultValue == ParamDefinition.NoDefaultValue)
                        {
                            builder.AppendFormat("\n  {0}", string.Format(Messages.ParamValueMissing, paramDef.Name));
                            var plugType = this;
                            var status = (long)(plugType.qualificationState | QualificationState.PlugParamValuesMissing);
                            plugType.qualificationState = (QualificationState)status;
                        }
                        else
                            paramValues.Add(
                                new ParamValue(
                                    this,
                                    new ParamValueAttribute(
                                        paramDef.Name,
                                        paramDef.DefaultValue,
                                        new[] { Name })));
                    }
                    else if (!paramDef.Type.IsInstanceOfType(qualifiedParamValue.Value))
                    {
                        var status = false;

                        try
                        {
                            var value = qualifiedParamValue.Value as string;

                            if (value != null)
                            {
                                var obj = ConvertFromString(value, paramDef.Type);
                                paramValues.Remove(qualifiedParamValue);

                                paramValues.Add(
                                    new ParamValue(
                                        this,
                                        new ParamValueAttribute(
                                            paramDef.Name,
                                            obj,
                                            new[] { Name })));

                                status = true;
                            }
                        }
                        catch (ArgumentException)
                        { }

                        if (!status)
                        {
                            builder.AppendFormat(
                                "\n  {0}",
                                string.Format(
                                    Messages.ParamValueNotAssignable,
                                    paramDef.Type.Name,
                                    paramDef.Name));

                            var plugType = this;
                            var stat = (long)(plugType.qualificationState | QualificationState.PlugParamValueNotAssignable);
                            plugType.qualificationState = (QualificationState)stat;
                        }
                    }
                }

                if (extensionTypeInfo.plugTypeInfos.
                    Any(plugType => plugType != this && plugType.Name == Name && plugType.IsQualified))
                {
                    builder.AppendFormat("\n  {0}", Messages.SlotAlreadyExists);
                    var plugTypeInfo = this;
                    var stat = (long)(plugTypeInfo.qualificationState | QualificationState.SlotNameAlreadyExists);
                    plugTypeInfo.qualificationState = (QualificationState)stat;
                }
            }

            var plugTyp = this;
            var stats = (long)(plugTyp.qualificationState & ~QualificationState.QualificationMissing);
            plugTyp.qualificationState = (QualificationState)stats;

            RuntimeBase.LogQualifyResult(
                this,
                qualificationState,
                QualificationState.QualificationMissing |
                QualificationState.PlugNameAlreadyExists |
                QualificationState.PlugSlotDefinitionNotFound |
                QualificationState.PlugSlotInterfaceNotImplemented |
                QualificationState.PlugParamValuesMissing |
                QualificationState.PlugParamValueNotAssignable,
                QualificationState.None,
                Messages.QualifyPlug,
                builder.ToString());

            return qualificationState;
        }

        internal QualificationState VerifyEventHandlerMethods(bool logResult)
        {
            return VerifyEventHandlerMethods(logResult, null);
        }

        internal QualificationState VerifyEventHandlerMethods(bool logResult, ICollection<ResultBase> invalidHandler)
        {
            var status = false;

            if (plugAttribute.OnPlugging == string.Empty &&
                plugAttribute.OnPlugged == string.Empty &&
                plugAttribute.OnUnplugging == string.Empty &&
                plugAttribute.OnUnplugged == string.Empty)
                eventHandlerMethodInfosSet = true;
            else
            {
                var type = extensionTypeInfo.type;

                if (!IsReflectionOnly && !eventHandlerMethodInfosSet)
                    status = true;

                var plugInfos = new PlugInfo[extensionTypeInfo.extensionInfos.Count];

                if (status)
                {
                    eventHandlerMethodInfosSet = true;

                    for (var i = 0; i < plugInfos.Length; ++i)
                        plugInfos[i] = extensionTypeInfo.extensionInfos[i].plugInfos[Name];
                }

                if (plugAttribute.OnPlugging != string.Empty)
                {
                    var implementingMethod = plugAttribute.OnPlugging;

                    var mismatchInfo =
                        invalidHandler != null
                            ? new List<IEnumerable<HandlerParameterInfo>>()
                            : null;

                    MiOnPlugging =
                        ExtensionTypeInfo.VerifyEventHandlerMethod(
                            type,
                            plugAttribute.OnPlugging,
                            Runtime.cancelPlugEventHandlerParamTypes,
                            string.Format(
                                Messages.PlugEventHandlerMethodDesc,
                                "OnPlugging",
                                Name),
                            logResult,
                            IsReflectionOnly,
                            mismatchInfo);

                    if (MiOnPlugging == null)
                    {
                        var plugTypeInfo = this;

                        var state = (long)(plugTypeInfo.qualificationState |
                            QualificationState.PlugOnPluggingMethodNotFound);

                        plugTypeInfo.qualificationState = (QualificationState)state;

                        if (mismatchInfo != null && mismatchInfo.Count > 0)
                            mismatchInfo.ForEach(
                                hm =>
                                invalidHandler.Add(
                                    new InvalidHandlerParameter(
                                        this,
                                        "OnPlugging",
                                        implementingMethod,
                                        hm)));

                        else if (mismatchInfo != null)
                            invalidHandler.Add(
                                new MissingHandler(
                                    this,
                                    "OnPlugging",
                                    implementingMethod));
                    }
                    else if (status)
                        foreach (var plugInfo in plugInfos)
                            plugInfo.Plugging +=
                                new ReflectionEventHandler(
                                    plugInfo.extensionInfo.Object,
                                    MiOnPlugging).InvokeMethod;
                }

                if (plugAttribute.OnPlugged != string.Empty)
                {
                    var implementingMethod = plugAttribute.OnPlugged;

                    var mismatchInfo =
                        invalidHandler != null
                            ? new List<IEnumerable<HandlerParameterInfo>>()
                            : null;

                    MiOnPlugged =
                        ExtensionTypeInfo.VerifyEventHandlerMethod(
                            type,
                            plugAttribute.OnPlugged,
                            Runtime.plugEventHandlerParamTypes,
                            string.Format(
                                Messages.PlugEventHandlerMethodDesc,
                                "OnPlugged",
                                Name),
                            logResult,
                            IsReflectionOnly,
                            mismatchInfo);

                    if (MiOnPlugged == null)
                    {
                        var plugTypeInfo = this;
                        var state = (long)(plugTypeInfo.qualificationState |
                            QualificationState.PlugOnPluggedMethodNotFound);

                        plugTypeInfo.qualificationState = (QualificationState)state;

                        if (mismatchInfo != null && mismatchInfo.Count > 0)
                            mismatchInfo.ForEach(
                                hm =>
                                invalidHandler.Add(
                                    new InvalidHandlerParameter(
                                        this,
                                        "OnPlugged",
                                        implementingMethod,
                                        hm)));

                        else if (mismatchInfo != null)
                            invalidHandler.Add(
                                new MissingHandler(
                                    this,
                                    "OnPlugged",
                                    implementingMethod));
                    }
                    else if (status)
                        foreach (var plugInfo in plugInfos)
                            plugInfo.Plugged +=
                                new ReflectionEventHandler(
                                    plugInfo.extensionInfo.Object,
                                    MiOnPlugged).InvokeMethod;
                }

                if (plugAttribute.OnUnplugging != string.Empty)
                {
                    var implementingMethod = plugAttribute.OnUnplugging;

                    var mismatchInfo = invalidHandler != null
                                           ? new List<IEnumerable<HandlerParameterInfo>>()
                                           : null;

                    MiOnUnplugging =
                        ExtensionTypeInfo.VerifyEventHandlerMethod(
                            type,
                            plugAttribute.OnUnplugging,
                            Runtime.cancelPlugEventHandlerParamTypes,
                            string.Format(
                                Messages.PlugEventHandlerMethodDesc,
                                "OnUnplugging",
                                Name),
                            logResult,
                            IsReflectionOnly,
                            mismatchInfo);

                    if (MiOnUnplugging == null)
                    {
                        var plugTypeInfo = this;
                        var state = (long)(plugTypeInfo.qualificationState |
                            QualificationState.PlugOnUnpluggingMethodNotFound);

                        plugTypeInfo.qualificationState = (QualificationState)state;

                        if (mismatchInfo != null && mismatchInfo.Count > 0)
                            mismatchInfo.ForEach(
                                hm =>
                                invalidHandler.Add(
                                    new InvalidHandlerParameter(
                                        this,
                                        "OnUnplugging",
                                        implementingMethod,
                                        hm)));

                        else if (mismatchInfo != null)
                            invalidHandler.Add(
                                new MissingHandler(
                                    this,
                                    "OnUnplugging",
                                    implementingMethod));
                    }
                    else if (status)
                        foreach (var plugInfo in plugInfos)
                            plugInfo.Unplugging +=
                                new ReflectionEventHandler(plugInfo.extensionInfo.Object, MiOnUnplugging).InvokeMethod;
                }

                if (plugAttribute.OnUnplugged != string.Empty)
                {
                    string implementingMethod = plugAttribute.OnUnplugged;

                    var mismatchInfo =
                        invalidHandler != null
                            ? new List<IEnumerable<HandlerParameterInfo>>()
                            : null;

                    MiOnUnplugged =
                        ExtensionTypeInfo.VerifyEventHandlerMethod(
                            type,
                            plugAttribute.OnUnplugged,
                            Runtime.plugEventHandlerParamTypes,
                            string.Format(
                                Messages.PlugEventHandlerMethodDesc,
                                "OnUnplugged",
                                Name),
                            logResult,
                            IsReflectionOnly,
                            mismatchInfo);

                    if (MiOnUnplugged == null)
                    {
                        var plugTypeInfo = this;
                        var state = (long)(plugTypeInfo.qualificationState |
                            QualificationState.PlugOnUnpluggedMethodNotFound);

                        plugTypeInfo.qualificationState = (QualificationState)state;

                        if (mismatchInfo != null && mismatchInfo.Count > 0)
                            mismatchInfo.ForEach(
                                hm =>
                                invalidHandler.Add(
                                    new InvalidHandlerParameter(
                                        this,
                                        "OnUnplugged",
                                        implementingMethod,
                                        hm)));

                        else if (mismatchInfo != null)
                            invalidHandler.Add(
                                new MissingHandler(
                                    this,
                                    "OnUnplugged",
                                    implementingMethod));
                    }
                    else if (status)
                        foreach (var plugInfo in plugInfos)
                            plugInfo.Unplugged +=
                                new ReflectionEventHandler(
                                    plugInfo.extensionInfo.Object, MiOnUnplugged).InvokeMethod;
                }
            }

            var plugType = this;
            var stat = (long)(plugType.qualificationState & ~QualificationState.MethodQualificationMissing);
            plugType.qualificationState = (QualificationState)stat;
            return qualificationState;
        }

        internal void OnPlugging(object sender, CancelPlugEventArgs args)
        {
            if (Plugging == null)
                return;

            Runtime.InvokeCancelEventHandler(
                string.Format(
                    Messages.PlugEventHandlerMethodDesc,
                    "Plugging",
                    Name),
                Plugging,
                sender,
                args);
        }

        internal void OnPlugged(object sender, PlugEventArgs args)
        {
            if (Plugged == null)
                return;

            Runtime.InvokeEventHandler(
                string.Format(
                    Messages.PlugEventHandlerMethodDesc,
                    "Plugged",
                    Name),
                Plugged,
                sender,
                args);
        }

        internal void OnUnplugging(object sender, CancelPlugEventArgs args)
        {
            if (Unplugging == null)
                return;

            Runtime.InvokeCancelEventHandler(
                string.Format(
                    Messages.PlugEventHandlerMethodDesc,
                    "Unplugging",
                    Name),
                Unplugging,
                sender,
                args);
        }

        internal void OnUnplugged(object sender, PlugEventArgs args)
        {
            if (Unplugged == null)
                return;

            Runtime.InvokeEventHandler(
                string.Format(
                    Messages.PlugEventHandlerMethodDesc,
                    "Unplugged",
                    Name),
                Unplugged,
                sender,
                args);
        }
    }
}

